package com.zheng.study.base.dao.impl;

import com.zheng.study.base.bean.search.Search;
import com.zheng.study.base.bean.search.SearchEnum;
import com.zheng.study.base.bean.search.order.Order;
import com.zheng.study.base.bean.search.page.PageList;
import com.zheng.study.base.bean.search.page.PagePaging;
import com.zheng.study.base.bean.search.page.PageUnique;
import com.zheng.study.base.bean.search.projection.ProjectionAggregate;
import com.zheng.study.base.bean.search.projection.ProjectionCount;
import com.zheng.study.base.bean.search.projection.ProjectionNone;
import com.zheng.study.base.bean.search.projection.ProjectionRowCount;
import com.zheng.study.base.bean.search.result.*;
import com.zheng.study.base.bean.search.rule.*;
import com.zheng.study.base.dao.BaseDao;
import com.zheng.study.base.entity.BaseEntity;
import com.zheng.study.base.utils.CommonUtil;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.*;
import org.hibernate.criterion.*;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * 基层dao的实现类
 * ============================================================================
 * author : dzw
 * copy : fallenpanda
 * createDate:  2018/5/25 。
 * ============================================================================
 */
@Repository
public abstract class BaseDaoImpl<T extends BaseEntity, PK extends Serializable> implements BaseDao<T, PK> {

	private Class<T> entityClass;

	@Resource
	protected SessionFactory sessionFactory;

	@SuppressWarnings("unchecked")
	public BaseDaoImpl(){
		this.entityClass = null;
		Class<?> c = getClass();
		Type type = c.getGenericSuperclass();
		if(type instanceof ParameterizedType){
			Type[] parameterizedType = ((ParameterizedType) type).getActualTypeArguments();
			this.entityClass = (Class<T>) parameterizedType[0];
		}
	}

	protected Class<T> getEntityClass() {
		return entityClass;
	}

	protected Session getSession(){
		return sessionFactory.getCurrentSession();
	}

	@SuppressWarnings("unchecked")
	@Override
	public T load(PK id){
		Assert.notNull(id, "id is required");
		return (T) getSession().load(entityClass, id);
	}

	@SuppressWarnings("unchecked")
	@Override
	public T get(PK id) {
		if(id == null){
			return null;
		}
		return (T)getSession().get(entityClass, id);
	}

	@SuppressWarnings("unchecked")
	@Override
	public PK save(T entity) {
		Assert.notNull(entity, "entity is required");
		return (PK) getSession().save(entity);
	}

	@Override
	public void delete(T entity) {
		Assert.notNull(entity, "entity is required");
		getSession().delete(entity);
	}

	@Override
	public void delete(PK id){
		Assert.notNull(id,"id is required");
		T entity = load(id);
		getSession().delete(entity);
	}

	@Override
	public void update(T entity) {
		Assert.notNull(entity , "entity is required");
		getSession().update(entity);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> get(PK[] ids){
		Assert.notEmpty(ids, "ids must not be empty");
		String hql = "from " + entityClass.getName() + " model where model.id in (:ids)";
		return getSession().createQuery(hql).setParameter("ids", ids).list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public T get(String propertyName, Object value){
		Assert.hasText(propertyName, "propertyName must not be empty");
		Assert.notNull(value, "value is required");
		String hql = "from " + entityClass.getName() + " model where model." + propertyName + "=?";
		return (T)getSession().createQuery(hql).setParameter(0, value).uniqueResult();
	}

	@Override
	public boolean isUnique(String propertyName, Object oldValue, Object newValue){
		Assert.hasText(propertyName, "propertyName must not be empty");
		Assert.notNull(newValue, "newValue is required");
		if(newValue == oldValue || newValue.equals(oldValue)){
			return true;
		}
		if(newValue instanceof String){
			if(oldValue != null && StringUtils.equalsAnyIgnoreCase((String)oldValue, (String)newValue)){
				return true;
			}
		}
		T object = get(propertyName, newValue);
		return object == null;
	}

	@Override
	public boolean isExist(String propertyName, Object value){
		Assert.hasText(propertyName, "propertyName must not be empty");
		Assert.notNull(value, "value is required");
		T object = get(propertyName, value);
		return (object!=null);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> getList(String propertyName, Object value){
		Assert.hasText(propertyName, "propertyName must not be empty");
		String hql;
		if(value == null){
			hql = "from " + entityClass.getName() + " model where model."+ propertyName +" is null";
			return getSession().createQuery(hql).list();
		}else{
			hql = "from " + entityClass.getName() + " model where model." + propertyName + "=?";
			return getSession().createQuery(hql).setParameter(0, value).list();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> getAll(){
		String hql = "from "+ entityClass.getName();
		return getSession().createQuery(hql).list();
	}

	@Override
	public Long getTotalCount(){
		String hql = "select count(*) from " + entityClass.getName();
		return (Long) getSession().createQuery(hql).uniqueResult();
	}

	@Override
	public void flush(){
		getSession().flush();
	}

	@Override
	public void clear(){
		getSession().clear();
	}

	@Override
	public void evict(Object object){
		Assert.notNull(object, "object is required");
		getSession().evict(object);
	}

	@Override
	public Query createQuery(String hql){
		return getSession().createQuery(hql);
	}

	@Override
	public SQLQuery createSqlQuery(String sql){
		return getSession().createSQLQuery(sql);
	}

	@Override
	public Object getUniqueResult(String hql){
		return getSession().createQuery(hql).uniqueResult();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> getList(String hql){
		return getSession().createQuery(hql).list();
	}

	/**
	 * 根据条件集合给query设置参数
	 * @param query query
	 * @param params map集合
	 */
	private void setParams(Query query, Map<String, Object> params){
		if(params != null && params.size() > 0){
			for(String key : params.keySet()){
				Object value = params.get(key);
				if(value instanceof Collection){
					query.setParameterList(key, (Collection<?>) value);
				}else if(value instanceof Object[]){
					query.setParameterList(key, (Object[]) value);
				}else if(value instanceof Date){
					query.setDate(key, (Date) value);
				}else{
					query.setParameter(key, value);
				}
			}
		}
	}

	@Override
	public Object getUniqueResult(String hql, Map<String, Object> params){
		Query query = createQuery(hql);
		setParams(query, params);
		return query.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> getList(String hql, Map<String, Object> params){
		Query query = createQuery(hql);
		setParams(query, params);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Object[]> getObjectList(String hql, Map<String, Object> params){
		Query query = createQuery(hql);
		setParams(query, params);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Map<String, Object>> getMapList(String hql, Map<String, Object> params) {
		Query query = createQuery(hql);
		setParams(query, params);
		return query.list();
	}

	@Override
	public SearchResult findBySearch(Search search) {
		if(search == null){
			search = Search.simpleSearchList();
		}

		DetachedCriteria detachedCriteria = DetachedCriteria.forClass(entityClass);
		Criteria criteria = detachedCriteria.getExecutableCriteria(getSession());

		List<String> aliasList = new ArrayList<String>();
		initRules(criteria, aliasList, search.getRules());

		if (search.getPage() instanceof PageUnique) {
			if (search.getPage().getProjection() instanceof ProjectionNone) {
				criteria.setProjection(null);
				criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
				return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
			} else if (search.getPage().getProjection() instanceof ProjectionRowCount) {
				criteria.setProjection(Projections.rowCount());// count(*)
				return SearchResultCount.countResult(search, (long) Integer.parseInt(criteria.uniqueResult().toString()));
			} else if (search.getPage().getProjection() instanceof ProjectionCount) {
				if (SearchEnum.ProjectionType.count.equals(search.getPage().getProjection().getProjectionType())) {
					criteria.setProjection(Projections.count(((ProjectionCount) search.getPage().getProjection()).getField()));// count(field)
					return SearchResultCount.countResult(search, Long.parseLong(criteria.uniqueResult().toString()));
				} else if (SearchEnum.ProjectionType.countDistinct.equals(search.getPage().getProjection().getProjectionType())) {
					criteria.setProjection(Projections.countDistinct(((ProjectionCount) search.getPage().getProjection()).getField()));// countDistinct(field)
					return SearchResultCount.countResult(search, Long.parseLong(criteria.uniqueResult().toString()));
				}
			} else if (search.getPage().getProjection() instanceof ProjectionAggregate) {
				String field = ((ProjectionAggregate) search.getPage().getProjection()).getField();

				if (SearchEnum.ProjectionType.max.equals(search.getPage().getProjection().getProjectionType())) {
					criteria.setProjection(Projections.max(field));// max(field)
					return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
				} else if (SearchEnum.ProjectionType.min.equals(search.getPage().getProjection().getProjectionType())) {
					criteria.setProjection(Projections.min(field));// min(field)
					return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
				} else if (SearchEnum.ProjectionType.avg.equals(search.getPage().getProjection().getProjectionType())) {
					criteria.setProjection(Projections.avg(field));// avg(field)
					return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
				} else if (SearchEnum.ProjectionType.sum.equals(search.getPage().getProjection().getProjectionType())) {
					criteria.setProjection(Projections.sum(field));// sum(field)
					return SearchResultUnique.uniqueResult(search, criteria.uniqueResult());
				}
			}
		} else if (search.getPage() instanceof PageList) {
			if (search.getPage() instanceof PagePaging) {
				// 计算总记录数
				criteria.setProjection(Projections.rowCount());
				long totalCount = Long.parseLong(criteria.uniqueResult().toString());

				criteria.setProjection(null);// 重置查询目标
				criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);

				// 添加排序
				initOrder(criteria, aliasList, search.getOrders());

				// 添加分页
				initPager(criteria, (PagePaging) search.getPage());

				List results = criteria.list();
				if (results == null) {
					results = new ArrayList();
				}

				long pageCount = 0;
				Integer pageSize = ((PagePaging) search.getPage()).getPageSize();
				if(pageSize == 0){
					return SearchResultPaging.list(search, ((PagePaging) search.getPage()).getPageNumber(), pageSize, totalCount, pageCount, results);
				}
				if (totalCount % pageSize == 0) {
					pageCount = totalCount / pageSize;
				} else {
					pageCount = (totalCount / pageSize) + 1;
				}

				return SearchResultPaging.list(search, ((PagePaging) search.getPage()).getPageNumber(), pageSize, totalCount, pageCount, results);
			} else {
				// 添加排序
				initOrder(criteria, aliasList, search.getOrders());

				List results = criteria.list();
				if (results == null) {
					results = new ArrayList();
				}

				return SearchResultList.list(search, results);
			}
		}


		return null;
	}

	private void initPager (Criteria criteria, PagePaging page) {
		if (page.getPageNumber() > 0) {
			criteria.setFirstResult((page.getPageNumber() - 1) * page.getPageSize());
			criteria.setMaxResults(page.getPageSize());
		}
	}

	private void initOrder(Criteria criteria, List<String> aliasList, List<Order> orders){
		if(orders != null && orders.size()>0){
			for(Order order:orders){
				String orderBy = order.getOrderBy();
				SearchEnum.OrderType orderType = order.getOrderType();

				if(StringUtils.isNotEmpty(orderBy) && orderType != null){
					// 获取 propertyName
					String propertyName = initPropertyName(criteria, aliasList, orderBy);
					if (SearchEnum.OrderType.asc.equals(orderType)) {
						criteria.addOrder(org.hibernate.criterion.Order.asc(propertyName));
					} else {
						criteria.addOrder(org.hibernate.criterion.Order.desc(propertyName));
					}
				}
			}
		}
	}

	private void initRules(Criteria criteria, List<String> aliasList, List<Rule> rules){
		if(rules != null && rules.size()>0){
			for(Rule rule : rules){
				Criterion criterion = getCriterion(criteria, aliasList, rule);
				if(criterion != null){
					criteria.add(criterion);
				}
			}
		}
	}

	private Criterion getCriterion(Criteria criteria, List<String> aliasList, Rule rule){
		if(rule instanceof RuleRelation){
			Rule[] rules = ((RuleRelation) rule).getRules();
			Criterion[] criterions = new Criterion[rules.length];
			for(int i=0;i<rules.length;i++){
				criterions[i] = getCriterion(criteria, aliasList, rules[i]);
			}

			if(SearchEnum.RuleType.and.equals(rule.getRuleType())){
				return Restrictions.and(criterions);
			}else if(SearchEnum.RuleType.or.equals(rule.getRuleType())){
				return Restrictions.or(criterions);
			}

		}else if(rule instanceof RuleNone){
			String field = ((RuleNone) rule).getFiled();
			String propertyName = initPropertyName(criteria, aliasList, field);

			if(SearchEnum.RuleType.isEmpty.equals(rule.getRuleType())){
				return Restrictions.isEmpty(propertyName);
			}else if(SearchEnum.RuleType.isNotEmpty.equals(rule.getRuleType())){
				return Restrictions.isNotEmpty(propertyName);
			}else if(SearchEnum.RuleType.isNull.equals(rule.getRuleType())){
				return Restrictions.isNull(propertyName);
			}else if(SearchEnum.RuleType.isNotNull.equals(rule.getRuleType())){
				return Restrictions.isNotNull(propertyName);
			}

		}else if(rule instanceof RuleSimple){
			String field = ((RuleSimple) rule).getField();
			Object value = ((RuleSimple) rule).getData();
			String propertyName = initPropertyName(criteria, aliasList, field);

			if (SearchEnum.RuleType.eq.equals(rule.getRuleType())){
				return Restrictions.eq(propertyName, value);
			} else if (SearchEnum.RuleType.ne.equals(rule.getRuleType())){
				return Restrictions.ne(propertyName, value);
			} else if (SearchEnum.RuleType.gt.equals(rule.getRuleType())){
				return Restrictions.gt(propertyName, value);
			} else if (SearchEnum.RuleType.ge.equals(rule.getRuleType())) {
				return Restrictions.ge(propertyName, value);
			} else if (SearchEnum.RuleType.lt.equals(rule.getRuleType())) {
				return Restrictions.lt(propertyName, value);
			} else if (SearchEnum.RuleType.le.equals(rule.getRuleType())) {
				return Restrictions.le(propertyName, value);
			} else if (SearchEnum.RuleType.in.equals(rule.getRuleType())) {
				if (value instanceof Object[]) {
					return Restrictions.in(propertyName, (Object[]) value);
				} else if (value instanceof Collection) {
					return Restrictions.in(propertyName, (Collection) value);
				}
			}

		}else if(rule instanceof RuleLike){
			String field = ((RuleLike) rule).getField();
			Object value = ((RuleLike) rule).getData();
			MatchMode matchMode = ((RuleLike) rule).getMatchMode();
			String propertyName = initPropertyName(criteria, aliasList, field);

			if(SearchEnum.RuleType.like.equals(rule.getRuleType())){
				if(matchMode!=null){
					return Restrictions.like(propertyName, (String)value, matchMode);
				}else{
					return Restrictions.like(propertyName, value);
				}
			}else if(SearchEnum.RuleType.ilike.equals(rule.getRuleType())){
				if (matchMode != null) {
					return Restrictions.ilike(propertyName, (String) value, matchMode);
				} else {
					return Restrictions.ilike(propertyName, value);
				}
			}

		}else if(rule instanceof RuleSql){
			String sql = ((RuleSql) rule).getSql();
			Object[] values = ((RuleSql) rule).getValues();
			org.hibernate.type.Type[] types = ((RuleSql) rule).getTypes();

			if(SearchEnum.RuleType.sql.equals(rule.getRuleType())){
				if(values != null && types != null){
					return Restrictions.sqlRestriction(sql, values, types);
				}else {
					return Restrictions.sqlRestriction(sql);
				}
			}
		}

		return null;
	}

	private String initPropertyName(Criteria criteria, List<String> aliasList, String field){
		String propertyName;
		if(StringUtils.contains(field,"_")){
			String[] fields = StringUtils.split(field,"_");
			for(int i=0;i<fields.length;i++){
				String alias = CommonUtil.getAlias(fields, i);
				if(!aliasList.contains(alias)){
					criteria.createAlias(CommonUtil.getPath(fields, i), alias);
					aliasList.add(alias);
				}
			}
			propertyName = CommonUtil.getPath(fields, fields.length-1);
		}else if(StringUtils.contains(field, ".")){
			String[] fields = StringUtils.split(field, ".");
			for (int j = 0; j < fields.length - 1; j++) {
				String alias = CommonUtil.getAlias(fields, j);
				if (!aliasList.contains(alias)) {
					criteria.createAlias(CommonUtil.getPath(fields, j), alias);
					aliasList.add(alias);
				}
			}
			propertyName = CommonUtil.getPath(fields, fields.length - 1);
		}else{
			propertyName = field;
		}
		return propertyName;
	}

	@Override
	public Object getObjectEntity(String keyId, String className) {
		String hql = "from "+className+" where id=:keyId ";
		List<Object> objectList = getSession().createQuery(hql).setParameter("keyId",keyId).list();
		if(objectList.size()>0){
			return objectList.get(0);
		}
		return null;
	}

}
